Utforsk kraftige TypeScript-alternativer til enums: const assertions og union-typer. Lær når du skal bruke hver for robust, vedlikeholdbar kode.
Mer enn Enums: TypeScript Const Assertions vs. Union-typer
I en verden av statisk typet JavaScript med TypeScript, har enums lenge vært en standardløsning for å representere et fast sett med navngitte konstanter. De tilbyr en klar og lesbar måte å definere en samling av relaterte verdier. Men etter hvert som prosjekter vokser og utvikler seg, søker utviklere ofte etter mer fleksible og noen ganger mer ytelseseffektive alternativer. To sterke utfordrere som ofte dukker opp er const assertions og union-typer. Dette innlegget dykker ned i nyansene ved å bruke disse alternativene til tradisjonelle enums, gir praktiske eksempler og veileder deg om når du skal velge hva.
Forståelse av tradisjonelle TypeScript Enums
Før vi utforsker alternativene, er det viktig å ha en solid forståelse av hvordan standard TypeScript enums fungerer. Enums lar deg definere et sett med navngitte numeriske eller strengkonstanter. De kan være numeriske (standard) eller strengbaserte.
Numeriske Enums
Som standard blir enum-medlemmer tildelt numeriske verdier fra og med 0.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
Du kan også eksplisitt tildele numeriske verdier.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
String Enums
String enums foretrekkes ofte for deres forbedrede feilsøkingsopplevelse, siden medlemsnavnene bevares i den kompilerte JavaScript-koden.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
Overheaden med Enums
Selv om enums er praktiske, kommer de med en liten overhead. Når de kompileres til JavaScript, blir TypeScript enums omgjort til objekter som ofte har omvendte mappinger (f.eks. å mappe den numeriske verdien tilbake til enum-navnet). Dette kan være nyttig, men bidrar også til pakkestørrelsen og er kanskje ikke alltid nødvendig.
Vurder denne enkle string enum-en:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
I JavaScript kan dette bli noe slikt:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
For enkle, skrivebeskyttede sett med konstanter, kan denne genererte koden føles litt overdreven.
Alternativ 1: Const Assertions
Const assertions er en kraftig TypeScript-funksjon som lar deg fortelle kompilatoren at den skal utlede den mest spesifikke typen som er mulig for en verdi. Når de brukes med arrays eller objekter ment for å representere et fast sett med verdier, kan de fungere som et lettvektsalternativ til enums.
Const Assertions med arrays
Du kan opprette en array av string-literaler og deretter bruke en const-påstand for å gjøre typen uforanderlig og elementene til literaltyper.
const statusArray = ["PENDING", "PROCESSING", "COMPLETED"] as const;
type StatusType = typeof statusArray[number];
let currentStatus: StatusType = "PROCESSING";
// currentStatus = "FAILED"; // Error: Type '"FAILED"' is not assignable to type 'StatusType'.
function processStatus(status: StatusType) {
console.log(`Processing status: ${status}`);
}
processStatus("COMPLETED");
La oss bryte ned hva som skjer her:
as const: Denne påstanden forteller TypeScript at den skal behandle arrayet som skrivebeskyttet og utlede de mest spesifikke literaltypene for elementene. Så, i stedet for `string[]`, blir typen `readonly ["PENDING", "PROCESSING", "COMPLETED"]`.typeof statusArray[number]: Dette er en mappet type. Den itererer over alle indeksene istatusArrayog trekker ut deres literaltyper.number-indekssignaturen sier i hovedsak "gi meg typen til ethvert element i dette arrayet." Resultatet er en union-type:"PENDING" | "PROCESSING" | "COMPLETED".
Denne tilnærmingen gir typesikkerhet lik string enums, men genererer minimalt med JavaScript. statusArray forblir et array av strenger i JavaScript.
Const Assertions med objekter
Const assertions er enda kraftigere når de brukes på objekter. Du kan definere et objekt der nøklene representerer dine navngitte konstanter og verdiene er de bokstavelige strengene eller tallene.
const userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
} as const;
type UserRole = typeof userRoles[keyof typeof userRoles];
let currentUserRole: UserRole = "EDITOR";
// currentUserRole = "GUEST"; // Error: Type '"GUEST"' is not assignable to type 'UserRole'.
function displayRole(role: UserRole) {
console.log(`User role is: ${role}`);
}
displayRole(userRoles.Admin); // Valid
displayRole("EDITOR"); // Valid
I dette objekt-eksemplet:
as const: Denne påstanden gjør hele objektet skrivebeskyttet. Enda viktigere er det at den utleder literaltyper for alle egenskapsverdier (f.eks."ADMIN"i stedet forstring) og gjør egenskapene selv skrivebeskyttede.keyof typeof userRoles: Dette uttrykket resulterer i en union av nøklene tiluserRoles-objektet, som er"Admin" | "Editor" | "Viewer".typeof userRoles[keyof typeof userRoles]: Dette er en oppslagstype. Den tar unionen av nøkler og bruker den til å slå opp de tilsvarende verdiene iuserRoles-typen. Dette resulterer i unionen av verdiene:"ADMIN" | "EDITOR" | "VIEWER", som er vår ønskede type for roller.
JavaScript-outputen for userRoles vil være et rent JavaScript-objekt:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
Dette er betydelig lettere enn en typisk enum.
Når skal man bruke Const Assertions
- Skrivebeskyttede konstanter: Når du trenger et fast sett med streng- eller tall-literaler som ikke skal endres under kjøring.
- Minimal JavaScript-output: Hvis du er bekymret for pakkestørrelse og ønsker den mest ytelseseffektive kjøretidsrepresentasjonen for konstantene dine.
- Objektlignende struktur: Når du foretrekker lesbarheten av nøkkel-verdi-par, likt hvordan du kanskje strukturerer data eller konfigurasjon.
- String-baserte sett: Spesielt nyttig for å representere tilstander, typer eller kategorier som best identifiseres av beskrivende strenger.
Alternativ 2: Union-typer
Union-typer lar deg erklære at en variabel kan holde en verdi av en av flere typer. Når de kombineres med literaltyper (streng-, tall-, boolske literaler), danner de en kraftig måte å definere et sett med tillatte verdier uten å trenge en eksplisitt konstantdeklarasjon for selve settet.
Union-typer med String-literaler
Du kan direkte definere en union av string-literaler.
type TrafficLightColor = "RED" | "YELLOW" | "GREEN";
let currentLight: TrafficLightColor = "YELLOW";
// currentLight = "BLUE"; // Error: Type '"BLUE"' is not assignable to type 'TrafficLightColor'.
function changeLight(color: TrafficLightColor) {
console.log(`Changing light to: ${color}`);
}
changeLight("RED");
// changeLight("REDDY"); // Error
Dette er den mest direkte og ofte den mest konsise måten å definere et sett med tillatte strengverdier på.
Union-typer med numeriske literaler
På samme måte kan du bruke numeriske literaler.
type HttpStatusCode = 200 | 400 | 404 | 500;
let responseCode: HttpStatusCode = 404;
// responseCode = 201; // Error: Type '201' is not assignable to type 'HttpStatusCode'.
function handleResponse(code: HttpStatusCode) {
if (code === 200) {
console.log("Success!");
} else {
console.log(`Error code: ${code}`);
}
}
handleResponse(500);
Når skal man bruke Union-typer
- Enkle, direkte sett: Når settet med tillatte verdier er lite, klart og ikke krever beskrivende nøkler utover selve verdiene.
- Implisitte konstanter: Når du ikke trenger å referere til en navngitt konstant for selve settet, men heller bruker de bokstavelige verdiene direkte.
- Maksimal konsishet: For enkle scenarioer der det å definere et dedikert objekt eller array føles som overkill.
- Funksjonsparametere/returtyper: Utmerket for å definere det nøyaktige settet med akseptable streng- eller tall-input/output for funksjoner.
Sammenligning av Enums, Const Assertions og Union-typer
La oss oppsummere de viktigste forskjellene og bruksområdene:
Kjøretidsatferd
- Enums: Genererer JavaScript-objekter, potensielt med omvendte mappinger.
- Const Assertions (Arrays/Objekter): Genererer rene JavaScript-arrays eller -objekter. Typeinformasjonen fjernes ved kjøring, men datastrukturen forblir.
- Union-typer (med literaler): Ingen kjøretidsrepresentasjon for selve unionen. Verdiene er bare literaler. Typesjekking skjer utelukkende ved kompileringstid.
Lesbarhet og uttrykksfullhet
- Enums: Høy lesbarhet, spesielt med beskrivende navn. Kan være mer ordrike.
- Const Assertions (Objekter): God lesbarhet gjennom nøkkel-verdi-par, etterligner konfigurasjoner eller innstillinger.
- Const Assertions (Arrays): Mindre lesbar for å representere navngitte konstanter, mer for bare en sortert liste med verdier.
- Union-typer: Veldig konsis. Lesbarheten avhenger av klarheten i selve literale verdiene.
Typesikkerhet
- Alle tre tilnærmingene tilbyr sterk typesikkerhet. De sikrer at kun gyldige, forhåndsdefinerte verdier kan tildeles variabler eller sendes til funksjoner.
Pakkestørrelse
- Enums: Generelt den største på grunn av genererte JavaScript-objekter.
- Const Assertions: Mindre enn enums, da de produserer rene datastrukturer.
- Union-typer: Den minste, da de ikke genererer noen spesifikk kjøretidsdatastruktur for selve typen, men bare stoler på literale verdier.
Bruksområde-matrise
Her er en rask guide:
| Egenskap | TypeScript Enum | Const Assertion (Objekt) | Const Assertion (Array) | Union-type (Literaler) |
|---|---|---|---|---|
| Kjøretidsoutput | JS-objekt (med omvendt mapping) | Rent JS-objekt | Rent JS-array | Ingen (kun literale verdier) |
| Lesbarhet (Navngitte konstanter) | Høy | Høy | Medium | Lav (verdier er navn) |
| Pakkestørrelse | Størst | Medium | Medium | Minst |
| Fleksibilitet | God | God | God | Utmerket (for enkle sett) |
| Vanlig bruk | Tilstander, Statuskoder, Kategorier | Konfigurasjon, Rolledefinisjoner, Feature-flagg | Sorterte lister med uforanderlige verdier | Funksjonsparametere, enkle begrensede verdier |
Praktiske eksempler og beste praksis
Eksempel 1: Representere API-statuskoder
Enum:
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Pending = "PENDING"
}
function handleApiResponse(status: ApiStatus) {
// ... logikk ...
}
Const Assertion (Objekt):
const apiStatusCodes = {
SUCCESS: "SUCCESS",
ERROR: "ERROR",
PENDING: "PENDING"
} as const;
type ApiStatus = typeof apiStatusCodes[keyof typeof apiStatusCodes];
function handleApiResponse(status: ApiStatus) {
// ... logikk ...
}
Union-type:
type ApiStatus = "SUCCESS" | "ERROR" | "PENDING";
function handleApiResponse(status: ApiStatus) {
// ... logikk ...
}
Anbefaling: For dette scenarioet er en union-type ofte den mest konsise og effektive. De literale verdiene er i seg selv beskrivende nok. Hvis du trengte å knytte ytterligere metadata til hver status (f.eks. en brukervennlig melding), ville et const assertion-objekt vært et bedre valg.
Eksempel 2: Definere brukerroller
Enum:
enum UserRoleEnum {
Admin = "ADMIN",
Moderator = "MODERATOR",
User = "USER"
}
function getUserPermissions(role: UserRoleEnum) {
// ... logikk ...
}
Const Assertion (Objekt):
const userRolesObject = {
Admin: "ADMIN",
Moderator: "MODERATOR",
User: "USER"
} as const;
type UserRole = typeof userRolesObject[keyof typeof userRolesObject];
function getUserPermissions(role: UserRole) {
// ... logikk ...
}
Union-type:
type UserRole = "ADMIN" | "MODERATOR" | "USER";
function getUserPermissions(role: UserRole) {
// ... logikk ...
}
Anbefaling: Et const assertion-objekt treffer en god balanse her. Det gir klare nøkkel-verdi-par (f.eks. userRolesObject.Admin) som kan forbedre lesbarheten når man refererer til roller, samtidig som det er ytelseseffektivt. En union-type er også en veldig sterk kandidat hvis direkte string-literaler er tilstrekkelig.
Eksempel 3: Representere konfigurasjonsalternativer
Tenk deg et konfigurasjonsobjekt for en global applikasjon som kan ha forskjellige temaer.
Enum:
enum Theme {
Light = "light",
Dark = "dark",
System = "system"
}
interface AppConfig {
theme: Theme;
// ... andre konfigurasjonsalternativer ...
}
Const Assertion (Objekt):
const themes = {
Light: "light",
Dark: "dark",
System: "system"
} as const;
type Theme = typeof themes[keyof typeof themes];
interface AppConfig {
theme: Theme;
// ... andre konfigurasjonsalternativer ...
}
Union-type:
type Theme = "light" | "dark" | "system";
interface AppConfig {
theme: Theme;
// ... andre konfigurasjonsalternativer ...
}
Anbefaling: For konfigurasjonsinnstillinger som temaer, er const assertion-objektet ofte ideelt. Det definerer tydelig de tilgjengelige alternativene og deres tilsvarende strengverdier. Nøklene (Light, Dark, System) er beskrivende og mapper direkte til verdiene, noe som gjør konfigurasjonskoden veldig forståelig.
Velge riktig verktøy for jobben
Avgjørelsen mellom TypeScript enums, const assertions og union-typer er ikke alltid svart-hvitt. Det handler ofte om en avveining mellom kjøretidsytelse, pakkestørrelse og kodelesbarhet/uttrykksfullhet.
- Velg Union-typer når du trenger et enkelt, begrenset sett med streng- eller tall-literaler og maksimal konsishet er ønsket. De er utmerkede for funksjonssignaturer og grunnleggende verdibegrensninger.
- Velg Const Assertions (med objekter) når du ønsker en mer strukturert, lesbar måte å definere navngitte konstanter på, likt en enum, men med betydelig mindre kjøretidsoverhead. Dette er flott for konfigurasjon, roller eller ethvert sett der nøklene gir betydelig mening.
- Velg Const Assertions (med arrays) når du bare trenger en uforanderlig, sortert liste med verdier, og direkte tilgang via indeks er viktigere enn navngitte nøkler.
- Vurder TypeScript Enums når du trenger deres spesifikke funksjoner, som omvendt mapping (selv om dette er mindre vanlig i moderne utvikling) eller hvis teamet ditt har en sterk preferanse og ytelsespåvirkningen er ubetydelig for prosjektet ditt.
I mange moderne TypeScript-prosjekter vil du finne en tendens mot const assertions og union-typer over tradisjonelle enums, spesielt for strengbaserte konstanter, på grunn av deres bedre ytelsesegenskaper og ofte enklere JavaScript-output.
Globale betraktninger
Når man utvikler applikasjoner for et globalt publikum, er konsistente og forutsigbare konstantdefinisjoner avgjørende. Valgene vi har diskutert (enums, const assertions, union-typer) bidrar alle til denne konsistensen ved å håndheve typesikkerhet på tvers av forskjellige miljøer og utvikler-lokasjoner.
- Konsistens: Uavhengig av valgt metode, er nøkkelen konsistens innad i prosjektet ditt. Hvis du bestemmer deg for å bruke const assertion-objekter for roller, hold deg til det mønsteret gjennom hele kodebasen.
- Internasjonalisering (i18n): Når man definerer etiketter eller meldinger som skal internasjonaliseres, bruk disse typesikre strukturene for å sikre at kun gyldige nøkler eller identifikatorer brukes. De faktiske oversatte strengene vil bli håndtert separat via i18n-biblioteker. For eksempel, hvis du har et `status`-felt som kan være "PENDING", "PROCESSING", "COMPLETED", vil ditt i18n-bibliotek mappe disse interne identifikatorene til lokalisert visningstekst.
- Tidssoner & Valutaer: Selv om det ikke er direkte relatert til enums, husk at når du håndterer verdier som datoer, tider eller valutaer, kan TypeScripts typesystem bidra til å håndheve korrekt bruk, men eksterne biblioteker er vanligvis nødvendige for nøyaktig global håndtering. For eksempel kan en `Currency` union-type defineres som `"USD" | "EUR" | "GBP"`, men den faktiske konverteringslogikken krever spesialiserte verktøy.
Konklusjon
TypeScript tilbyr et rikt sett med verktøy for å håndtere konstanter. Selv om enums har tjent oss godt, tilbyr const assertions og union-typer overbevisende, ofte mer ytelseseffektive, alternativer. Ved å forstå forskjellene deres og velge riktig tilnærming basert på dine spesifikke behov – enten det er ytelse, lesbarhet eller konsishet – kan du skrive mer robust, vedlikeholdbar og effektiv TypeScript-kode som skalerer globalt.
Å omfavne disse alternativene kan føre til mindre pakkestørrelser, raskere applikasjoner og en mer forutsigbar utvikleropplevelse for ditt internasjonale team.